{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "toc",
        "id": "44u-b_TBga8O"
      },
      "source": [
        "\u003e[Getting started](#scrollTo=JqL6-Dnri-nI)\n",
        "\n",
        "\u003e\u003e[Imports](#scrollTo=j_FYRZOYR2Cx)\n",
        "\n",
        "\u003e\u003e[Defining the KG schema](#scrollTo=jC_zFFk5RwyL)\n",
        "\n",
        "\u003e\u003e[Loading KG triples](#scrollTo=j07E2VBsR7EX)\n",
        "\n",
        "\u003e[Following relations in the KG](#scrollTo=dRrFTN4_u6FW)\n",
        "\n",
        "\u003e[Conditionals](#scrollTo=qxaVy-iffD8u)\n",
        "\n",
        "\u003e[Quantifying over relations](#scrollTo=7cyyZan-zrDe)\n",
        "\n",
        "\u003e[Under the hood - NQL and TensorFlow](#scrollTo=K7DOLcCbfPWW)\n",
        "\n",
        "\u003e\u003e[Weighted sets and counting paths](#scrollTo=f8WxHlQNTPKG)\n",
        "\n",
        "\u003e\u003e[Why weights are useful](#scrollTo=t6IMb1iOgJSC)\n",
        "\n",
        "\u003e\u003e[Following relations via sparse matrix multiplication](#scrollTo=3m10zmsqY_37)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "JqL6-Dnri-nI"
      },
      "source": [
        "# Getting started"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "j_FYRZOYR2Cx"
      },
      "source": [
        "## Imports"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "eJSD8TdTjCFH"
      },
      "source": [
        "NQL is a PathQuery-like language which is closely integrated with Tensorflow.  This lab walks you through some simple use cases for NQL, the Neural Query Language.  The focus is on explaining the semantics of the query language - another CoLab looks at using NQL in conjunction with Tensorflow for learning."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 904
        },
        "colab_type": "code",
        "id": "-qgAPxUojWWn",
        "outputId": "f7077567-e362-41a6-b3aa-16def9b9302d"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "/content/language_repo/language/nql\n",
            "/content/language_repo/language/nql\n",
            "Processing /content/language_repo/language/nql\n",
            "Requirement already satisfied: tensorflow-gpu in /usr/local/lib/python3.6/dist-packages (from nql==0.0.1.dev0) (2.1.0)\n",
            "Requirement already satisfied: scipy in /usr/local/lib/python3.6/dist-packages (from nql==0.0.1.dev0) (1.4.1)\n",
            "Requirement already satisfied: mock in /usr/local/lib/python3.6/dist-packages (from nql==0.0.1.dev0) (4.0.2)\n",
            "Requirement already satisfied: numpy in /usr/local/lib/python3.6/dist-packages (from nql==0.0.1.dev0) (1.18.2)\n",
            "Requirement already satisfied: tensorflow-estimator\u003c2.2.0,\u003e=2.1.0rc0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (2.1.0)\n",
            "Requirement already satisfied: google-pasta\u003e=0.1.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (0.2.0)\n",
            "Requirement already satisfied: grpcio\u003e=1.8.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (1.27.2)\n",
            "Requirement already satisfied: six\u003e=1.12.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (1.12.0)\n",
            "Requirement already satisfied: astor\u003e=0.6.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (0.8.1)\n",
            "Requirement already satisfied: keras-preprocessing\u003e=1.1.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (1.1.0)\n",
            "Requirement already satisfied: gast==0.2.2 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (0.2.2)\n",
            "Requirement already satisfied: keras-applications\u003e=1.0.8 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (1.0.8)\n",
            "Requirement already satisfied: termcolor\u003e=1.1.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (1.1.0)\n",
            "Requirement already satisfied: protobuf\u003e=3.8.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (3.10.0)\n",
            "Requirement already satisfied: absl-py\u003e=0.7.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (0.9.0)\n",
            "Requirement already satisfied: tensorboard\u003c2.2.0,\u003e=2.1.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (2.1.1)\n",
            "Requirement already satisfied: opt-einsum\u003e=2.3.2 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (3.2.0)\n",
            "Requirement already satisfied: wheel\u003e=0.26; python_version \u003e= \"3\" in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (0.34.2)\n",
            "Requirement already satisfied: wrapt\u003e=1.11.1 in /usr/local/lib/python3.6/dist-packages (from tensorflow-gpu-\u003enql==0.0.1.dev0) (1.12.1)\n",
            "Requirement already satisfied: h5py in /usr/local/lib/python3.6/dist-packages (from keras-applications\u003e=1.0.8-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (2.10.0)\n",
            "Requirement already satisfied: setuptools in /usr/local/lib/python3.6/dist-packages (from protobuf\u003e=3.8.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (46.1.3)\n",
            "Requirement already satisfied: google-auth\u003c2,\u003e=1.6.3 in /usr/local/lib/python3.6/dist-packages (from tensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (1.7.2)\n",
            "Requirement already satisfied: requests\u003c3,\u003e=2.21.0 in /usr/local/lib/python3.6/dist-packages (from tensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (2.21.0)\n",
            "Requirement already satisfied: google-auth-oauthlib\u003c0.5,\u003e=0.4.1 in /usr/local/lib/python3.6/dist-packages (from tensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (0.4.1)\n",
            "Requirement already satisfied: werkzeug\u003e=0.11.15 in /usr/local/lib/python3.6/dist-packages (from tensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (1.0.1)\n",
            "Requirement already satisfied: markdown\u003e=2.6.8 in /usr/local/lib/python3.6/dist-packages (from tensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (3.2.1)\n",
            "Requirement already satisfied: pyasn1-modules\u003e=0.2.1 in /usr/local/lib/python3.6/dist-packages (from google-auth\u003c2,\u003e=1.6.3-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (0.2.8)\n",
            "Requirement already satisfied: cachetools\u003c3.2,\u003e=2.0.0 in /usr/local/lib/python3.6/dist-packages (from google-auth\u003c2,\u003e=1.6.3-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (3.1.1)\n",
            "Requirement already satisfied: rsa\u003c4.1,\u003e=3.1.4 in /usr/local/lib/python3.6/dist-packages (from google-auth\u003c2,\u003e=1.6.3-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (4.0)\n",
            "Requirement already satisfied: idna\u003c2.9,\u003e=2.5 in /usr/local/lib/python3.6/dist-packages (from requests\u003c3,\u003e=2.21.0-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (2.8)\n",
            "Requirement already satisfied: chardet\u003c3.1.0,\u003e=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests\u003c3,\u003e=2.21.0-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (3.0.4)\n",
            "Requirement already satisfied: urllib3\u003c1.25,\u003e=1.21.1 in /usr/local/lib/python3.6/dist-packages (from requests\u003c3,\u003e=2.21.0-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (1.24.3)\n",
            "Requirement already satisfied: certifi\u003e=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests\u003c3,\u003e=2.21.0-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (2019.11.28)\n",
            "Requirement already satisfied: requests-oauthlib\u003e=0.7.0 in /usr/local/lib/python3.6/dist-packages (from google-auth-oauthlib\u003c0.5,\u003e=0.4.1-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (1.3.0)\n",
            "Requirement already satisfied: pyasn1\u003c0.5.0,\u003e=0.4.6 in /usr/local/lib/python3.6/dist-packages (from pyasn1-modules\u003e=0.2.1-\u003egoogle-auth\u003c2,\u003e=1.6.3-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (0.4.8)\n",
            "Requirement already satisfied: oauthlib\u003e=3.0.0 in /usr/local/lib/python3.6/dist-packages (from requests-oauthlib\u003e=0.7.0-\u003egoogle-auth-oauthlib\u003c0.5,\u003e=0.4.1-\u003etensorboard\u003c2.2.0,\u003e=2.1.0-\u003etensorflow-gpu-\u003enql==0.0.1.dev0) (3.1.0)\n",
            "Building wheels for collected packages: nql\n",
            "  Building wheel for nql (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "  Created wheel for nql: filename=nql-0.0.1.dev0-cp36-none-any.whl size=48759 sha256=a9c2a673a44f830cdc58babff153e9c5521a61208cdc77a39503be00c307621b\n",
            "  Stored in directory: /tmp/pip-ephem-wheel-cache-3qoxtlh9/wheels/51/1e/3d/f92e27698ae7f6d586fd5482f569704ed8ba1a16f293bb0a5c\n",
            "Successfully built nql\n",
            "Installing collected packages: nql\n",
            "  Found existing installation: nql 0.0.1.dev0\n",
            "    Uninstalling nql-0.0.1.dev0:\n",
            "      Successfully uninstalled nql-0.0.1.dev0\n",
            "Successfully installed nql-0.0.1.dev0\n",
            "/root\n",
            "/root\n"
          ]
        }
      ],
      "source": [
        "%tensorflow_version 2.x\n",
        "!test -d language_repo || git clone https://github.com/google-research/language.git language_repo\n",
        "%cd /content/language_repo/language/nql\n",
        "!pwd\n",
        "!pip install .\n",
        "%cd\n",
        "!pwd"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "x1l0-bpKLVSj"
      },
      "outputs": [],
      "source": [
        "import math\n",
        "import time\n",
        "import importlib\n",
        "\n",
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "import nql\n",
        "from nql import util\n",
        "  \n",
        "# Prints a dictionary or list in a sorted way.\n",
        "def printd(obj, top=None):\n",
        "  if isinstance(obj, dict):\n",
        "    print(sorted(obj.items())[:top])\n",
        "  elif isinstance(obj, list):\n",
        "    print(sorted(obj)[:top])\n",
        "  else:\n",
        "    print(obj)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "jC_zFFk5RwyL"
      },
      "source": [
        "## Defining the KG schema"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "gasvtLlImrL-"
      },
      "source": [
        "Now, let's set up a small KG to run over.  The one we'll work with is inspired by a [very old Hinton paper on learning relations](http://www.cs.toronto.edu/~fritz/absps/icml-lre.pdf). It has one type - a person type - and twelve relations, which correspond to familial relations. \n",
        "\n",
        "We start out by setting up a context object, to hold the data, and defining a schema.  By convention a type name will end with \"_t\". When we declare a relation, we will provide the name of the type of its first and second arguments.  So to define the schema for this KG we will do this:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "colab_type": "code",
        "id": "6Ogjh_Y_nI38",
        "outputId": "e416725b-abcf-46f0-854f-a60b9762f672"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "declared relations ['aunt', 'brother', 'daughter', 'father', 'husband', 'mother', 'nephew', 'niece', 'sister', 'son', 'uncle', 'wife']\n",
            "declared types ['person_t']\n"
          ]
        }
      ],
      "source": [
        "context = nql.NeuralQueryContext()\n",
        "relations = [\n",
        "  'aunt', 'brother', 'daughter', 'father', 'husband', 'mother', \n",
        "  'nephew', 'niece', 'sister', 'son', 'uncle', 'wife' \n",
        "]\n",
        "for r in relations:\n",
        "  context.declare_relation(r, 'person_t', 'person_t')\n",
        "\n",
        "print('declared relations', sorted(context.get_relation_names()))\n",
        "print('declared types', sorted(context.get_type_names()))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "j07E2VBsR7EX"
      },
      "source": [
        "## Loading KG triples"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "jsAKh7uSq-pB"
      },
      "source": [
        "Now let's load in some data.  These are the relationships between members of several European royal families.  Loading these will define the relations that we declared."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "xUc3nGJRrHMY",
        "outputId": "faabdb51-f642-434a-ed95-3139a48115bd"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:0 facts loaded\n"
          ]
        }
      ],
      "source": [
        "with open('/content/language_repo/language/nql/demos/data/royal92/royal_family.tsv') as f:\n",
        "  context.load_kg(files=f)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "NSGqMqYft-Ko"
      },
      "source": [
        "The original [input](https://github.com/google-research/language/tree/master/language/nql/demos/data/royal92/royal_family.tsv) is stored as tab-separated triples, each triple being a relation name, followed by a person name.  For instance the first few lines are as follows."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "785yEutBuUaP",
        "outputId": "7ce586ce-8058-46ad-e555-c3e229ee35a3"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "['husband\\tVictoria of house of Hanover\\tAlbert Augustus Charles\\n', 'wife\\tAlbert Augustus Charles\\tVictoria of house of Hanover\\n', 'father\\tVictoria Adelaide Mary\\tAlbert Augustus Charles\\n']\n"
          ]
        }
      ],
      "source": [
        "with open('/content/language_repo/language/nql/demos/data/royal92/royal_family.tsv') as f:\n",
        "  print(f.readlines()[:3])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "dRrFTN4_u6FW"
      },
      "source": [
        "# Following relations in the KG"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "zlced6Z3Qx94"
      },
      "source": [
        "Now that a KG is loaded in, we can use the context object as a factory to create some NQL expressions.  The *one* method creates a singleton set containing one entity, which is specified via its name and type."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 51
        },
        "colab_type": "code",
        "id": "fDCpffVlw8nY",
        "outputId": "81ef2bc3-e95c-46b8-cfd0-2b303d1a109a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{'Victoria of house of Hanover': 1.0}\n",
            "{'Henry_VIII of house of Tudor': 1.0}\n"
          ]
        }
      ],
      "source": [
        "queen_vic = context.one('Victoria of house of Hanover', 'person_t')\n",
        "henry8 = context.one('Henry_VIII of house of Tudor', 'person_t')\n",
        "\n",
        "print(queen_vic.eval())\n",
        "print(henry8.eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "aVoO9KXkvAPl"
      },
      "source": [
        "Expressions in NQL always evaluate to *sets* of entities.  So, if you want to inspect a particular entity, then you need to construct a singleton set containing that entity.    For example, the expressions below find people in the KG (i.e., entities of type 'person_t').  Like Tensorflow expressions NQL expressions are lazy, and you should evaluate them in the context of a session.  "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "_H0ebbtOye5d"
      },
      "source": [
        "The default output of *nql_expression*.eval() is a Python dictionary mapping entity names to floats.  The floats make it possible to have a \"soft\" or weighted set of entities.  We'll come back to this feature later, but for now we can ignore the floats."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "0CoEIe8r2hTz"
      },
      "source": [
        "We can also follow relations in the KG, for example, asking 'who were the wives of Henry VIII?'"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "eqa0A-L_2rQv",
        "outputId": "aefc2740-fe00-4372-8996-6aab69244da3"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Anne of house of Boleyn', 1.0), ('Anne of_Cleves', 1.0), ('Catherine of house of Howard', 1.0), ('Catherine of house of Parr', 1.0), ('Catherine of_Aragon', 1.0), ('Jane of house of Seymour', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "printd(henry8.wife().eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "eUnux3Vx3JrX"
      },
      "source": [
        "Alternatively you can follow relations by name, or follow the inverse of a relation.  You can also chain relations."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 105
        },
        "colab_type": "code",
        "id": "leZ-qScL3Uvv",
        "outputId": "a987fc1e-0b1a-4622-8d45-5c8edb667e9c"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Albert Augustus Charles', 1.0)]\n",
            "[('Albert Augustus Charles', 1.0)]\n",
            "[('Albert Augustus Charles', 1.0)]\n",
            "[('Albert Victor Christian', 1.0), ('Alfred', 1.0), ('Arthur of_Connaught', 1.0), ('Charles Edward', 1.0), ('George_V of house of Windsor', 1.0), ('John Alexander', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "printd(queen_vic.follow('husband').eval())\n",
        "printd(queen_vic.follow('wife', -1).eval())\n",
        "printd(queen_vic.wife(-1).eval())\n",
        "printd(queen_vic.son().son().eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "yK5Coyhv5T7n"
      },
      "source": [
        "You can take unions and intersections of sets."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 156
        },
        "colab_type": "code",
        "id": "qw43ghK3FEhQ",
        "outputId": "a4142643-b7f4-4d5a-b114-53e897f0d0cd"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "queen victoria's children\n",
            " - sons:\n",
            "[('Alfred Ernest Albert', 1.0), ('Arthur William Patrick', 1.0), ('Edward_VII of house of Wettin', 1.0), ('Leopold George Duncan', 1.0)]\n",
            " - daughters:\n",
            "[('Alice Maud Mary', 1.0), ('Beatrice Mary Victoria', 1.0), ('Helena Augusta Victoria', 1.0), ('Louise Caroline Alberta', 1.0), ('Victoria Adelaide Mary', 1.0)]\n",
            " - all:\n",
            "[('Alfred Ernest Albert', 1.0), ('Alice Maud Mary', 1.0), ('Arthur William Patrick', 1.0), ('Beatrice Mary Victoria', 1.0), ('Edward_VII of house of Wettin', 1.0), ('Helena Augusta Victoria', 1.0), ('Leopold George Duncan', 1.0), ('Louise Caroline Alberta', 1.0), ('Victoria Adelaide Mary', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "print('queen victoria\\'s children')\n",
        "print(' - sons:')\n",
        "printd(queen_vic.son().eval())\n",
        "\n",
        "print(' - daughters:')\n",
        "printd(queen_vic.daughter().eval())\n",
        "\n",
        "print(' - all:')\n",
        "printd((queen_vic.son() | queen_vic.daughter()).eval())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "0n_iqzJq6N3N",
        "outputId": "26172c06-7ecd-4dcd-d3a0-d59543968080"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Catherine of_Aragon', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "printd((henry8.wife() \u0026 context.one('Ferdinand_V', 'person_t').daughter()).eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "FPd_UOFSTlQl"
      },
      "source": [
        "(By the  way, * and + are synonyms for \u0026 and |, so you can use either notation)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "TN9QN3qY64pL"
      },
      "source": [
        "And as in TensorFlow, you can use Python functions which return expressions as macros, to build up more complex relationships."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "colab_type": "code",
        "id": "WoiR9fRG7Doq",
        "outputId": "422116c9-9242-4989-8fee-4f550b3c0cd8"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Alfred Ernest Albert', 1.0), ('Alice Maud Mary', 1.0), ('Arthur William Patrick', 1.0), ('Beatrice Mary Victoria', 1.0), ('Edward_VII of house of Wettin', 1.0), ('Helena Augusta Victoria', 1.0), ('Leopold George Duncan', 1.0), ('Louise Caroline Alberta', 1.0), ('Victoria Adelaide Mary', 1.0)]\n",
            "[('Edmund of house of Tudor #2', 1.0), ('Edward_IV', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "def child(x): return x.son() | x.daughter()\n",
        "def parent(x): return x.mother() | x.father()\n",
        "def grandfather(x): return parent(x).father()\n",
        "\n",
        "printd(child(queen_vic).eval())\n",
        "printd(grandfather(henry8).eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "RgNf1BxFT5J_"
      },
      "source": [
        ""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "4mDoXzbjR3Pu"
      },
      "source": [
        "Also notice that the newly-defined constructs are functions: e.g., *male(S)* returns a new set *S'* which contains the male members of *S.*  If it seems awkward to switch from \"chaining-style\" syntax (e.g., *x.son().son()*) to \"function-call\" syntax, there is a way around that, which we will discuss below."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qxaVy-iffD8u"
      },
      "source": [
        "# Conditionals #"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "NL8MZIS2_rNX"
      },
      "source": [
        "This dataset has no sex or gender indications, so here's a way to identify the men and women in the dataset.  Note the use of the new construct *context.all(type)* which returns a set of all entities in a type."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "colab_type": "code",
        "id": "BNs5UGNl77aS",
        "outputId": "01b11bda-93aa-46f1-d0f1-3e45571d2d96"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Adalbert', 7.0), ('Adalbert #2', 8.0), ('Adolphe of_Luxembourg', 2.0), ('Adolphus', 3.0), ('Adolphus 2nd', 4.0)]\n",
            "[('(Daughter)', 11.0), ('(Sophia) Charlotte', 1.0), ('Adela', 10.0), ('Adelaide', 4.0), ('Adelaide Horatia Elizabeth of house of Seymour', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "def male(x): return (x.mother().son() \u0026 x)\n",
        "def female(x): return (x.mother().daughter() \u0026 x)\n",
        "\n",
        "printd(male(context.all('person_t')).eval(), 5)\n",
        "printd(female(context.all('person_t')).eval(), 5)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "AZrs5vqbSWLJ"
      },
      "source": [
        "Note that the weights in these sets are not uniform -- we'll get back to this below, in the \"under the hood\" section."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "cK7K6n0dfqWV"
      },
      "source": [
        "Now, however, we will use *male* and *female* to demonstrate the syntax for conditional relation-following.  As a somewhat artificial example, suppose we want to define *opposite_sex_parent* as the father of a female person, and mother of a male person.  We could do this as follows."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 85
        },
        "colab_type": "code",
        "id": "PanO14vAgG20",
        "outputId": "b4bf2de4-416f-4d4b-c431-c303602b6cf5"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "opposite_sex_parent of henry:\n",
            "[('Elizabeth of_York', 1.0)]\n",
            "opposite_sex_parent of victoria:\n",
            "[('Edward Augustus of house of Hanover', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "def opposite_sex_parent(x):\n",
        "  return x.father().if_any(female(x)) | x.mother().if_any(male(x))\n",
        "\n",
        "print('opposite_sex_parent of henry:')\n",
        "printd(opposite_sex_parent(henry8).eval())\n",
        "print('opposite_sex_parent of victoria:')\n",
        "printd(opposite_sex_parent(queen_vic).eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "7cyyZan-zrDe"
      },
      "source": [
        "# Quantifying over relations"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "U16KoU3xzw_d"
      },
      "source": [
        "NQL lets you \"follow\" specific relations.  Sometimes it's useful to variabilize the relations you follow.  We already saw the syntax *x.follow(r)* where *r* is a Python variable bound to a string naming a relation.  Here we'll discuss a way to push these \"relation variables\" down into Tensorflow."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "7RenP3ZA7ICF"
      },
      "source": [
        "We will first construct a \"relation group\" consisting of a selected subset of relations.  By convention relation group names end with \"_g\".  You usually won't need the return value of *construct_relation_group* but it's printed to help explain what's happens under the hood, below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "J_rFbj4B7PPF",
        "outputId": "0ad3e7e8-8f9f-42ae-cfb2-a561e07b7dd8"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('members', ['son', 'daughter', 'father', 'mother', 'brother', 'sister', 'wife', 'husband']), ('name', 'nuclear_family_g'), ('object_rel', 'nuclear_family_g_obj'), ('relation_rel', 'nuclear_family_g_rel'), ('subject_rel', 'nuclear_family_g_subj'), ('triple_prefix', 'nuclear_family_g_trip_'), ('triple_type', 'nuclear_family_g_triple_t'), ('weight_rel', 'nuclear_family_g_weight')]\n"
          ]
        }
      ],
      "source": [
        "nuclear_family_relations = ['son', 'daughter', 'father', 'mother', 'brother', 'sister', 'wife', 'husband']\n",
        "g = context.construct_relation_group('nuclear_family_g', 'person_t', 'person_t', nuclear_family_relations)\n",
        "printd(g.__dict__)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Cqk8RUBK_1j7"
      },
      "source": [
        "The effect of this call is to add two new types and four new relations to the KG."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 173
        },
        "colab_type": "code",
        "id": "xYMgBgadAslI",
        "outputId": "1bf044bd-8a65-4da0-fb28-8d155b21547a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "all relations:\n",
            "dict_keys(['aunt', 'brother', 'daughter', 'father', 'husband', 'mother', 'nephew', 'niece', 'sister', 'son', 'uncle', 'wife', 'nuclear_family_g_rel', 'nuclear_family_g_subj', 'nuclear_family_g_obj', 'nuclear_family_g_weight'])\n",
            "all types:\n",
            "dict_keys(['person_t', 'nuclear_family_g', 'nuclear_family_g_triple_t'])\n",
            "new relations:\n",
            "['nuclear_family_g_obj', 'nuclear_family_g_rel', 'nuclear_family_g_subj', 'nuclear_family_g_weight']\n",
            "new types:\n",
            "['nuclear_family_g', 'nuclear_family_g_triple_t']\n"
          ]
        }
      ],
      "source": [
        "print('all relations:')\n",
        "printd(context.get_relation_names())\n",
        "print('all types:')\n",
        "printd(context.get_type_names())\n",
        "print('new relations:')\n",
        "printd(list(filter(lambda x:x.startswith(g.name), context.get_relation_names())))\n",
        "print('new types:')\n",
        "printd(list(filter(lambda x:x.startswith(g.name), context.get_type_names())))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "l9I7Qoy_CIDm"
      },
      "source": [
        "The instances of *nuclear_family_g* are the relations listed on *construct_relation_group*."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 153
        },
        "colab_type": "code",
        "id": "uwEiihl-CbID",
        "outputId": "5aa15d79-c897-4fde-b8ed-b7169ad92e58"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "son\n",
            "daughter\n",
            "father\n",
            "mother\n",
            "brother\n",
            "sister\n",
            "wife\n",
            "husband\n"
          ]
        }
      ],
      "source": [
        "for rel_id in range(context.get_max_id('nuclear_family_g')):\n",
        "  print(context.get_entity_name(rel_id, 'nuclear_family_g'))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "pG6iDQNgC5Wi"
      },
      "source": [
        "We can now define sets of relations the same way that we can define sets of entities, for example"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "n7mVBwusDTDo",
        "outputId": "4a16a94a-9eb2-411e-a9f4-ae21e1e1ff94"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('husband', 1.0), ('wife', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "spouse = context.one('wife', 'nuclear_family_g') | context.one('husband', 'nuclear_family_g')\n",
        "printd(spouse.eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "g8v57z-_Dny4"
      },
      "source": [
        "NQL lets you follow a group of relations, much as you can follow a single relation."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "colab_type": "code",
        "id": "euXGTceQD7V2",
        "outputId": "ddcb9446-1418-4796-bbb9-6c850c614632"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Anne of house of Boleyn', 1.0), ('Anne of_Cleves', 1.0), ('Catherine of house of Howard', 1.0), ('Catherine of house of Parr', 1.0), ('Catherine of_Aragon', 1.0), ('Jane of house of Seymour', 1.0)]\n",
            "[('Albert Augustus Charles', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "printd(henry8.follow(spouse).eval())\n",
        "printd(queen_vic.follow(spouse).eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "MA_4v_rlEPOi"
      },
      "source": [
        "As another example, these are all of the members of Queen Victoria's nuclear family."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "WR44p-zFEd6O",
        "outputId": "51116cd3-326b-4df3-b126-47a04f64f32d"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Albert Augustus Charles', 1.0), ('Alfred Ernest Albert', 1.0), ('Alice Maud Mary', 1.0), ('Arthur William Patrick', 1.0), ('Beatrice Mary Victoria', 1.0), ('Edward Augustus of house of Hanover', 1.0), ('Edward_VII of house of Wettin', 1.0), ('Helena Augusta Victoria', 1.0), ('Leopold George Duncan', 1.0), ('Louise Caroline Alberta', 1.0), ('Victoria Adelaide Mary', 1.0), ('Victoria Mary Louisa', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "printd(queen_vic.follow(context.all('nuclear_family_g')).eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "3EiXa9nGEvaN"
      },
      "source": [
        "Under the hood, this is implemented using the other new type, the triple type.  Let's look at a particular triple, which one which encodes the fact Queen Victoria's husband is Prince Albert. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 51
        },
        "colab_type": "code",
        "id": "rBDSogJQFFLO",
        "outputId": "9a6594a6-0299-4300-f49c-0f792393ea78"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('nuclear_family_g_trip_14295', 1.0)]\n",
            "14295\n"
          ]
        }
      ],
      "source": [
        "trip = queen_vic.follow('nuclear_family_g_subj',-1) \u0026 spouse.follow('nuclear_family_g_rel',-1)\n",
        "printd(trip.eval())\n",
        "print(context.get_id(list(trip.eval().keys())[0], 'nuclear_family_g_triple_t'))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 153
        },
        "colab_type": "code",
        "id": "gJOqUBUMICbt",
        "outputId": "fca47d65-23f5-44f3-a7aa-9638d2164418"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "triple t:\n",
            "[('nuclear_family_g_trip_14295', 1.0)]\n",
            "subject of t:\n",
            "[('Victoria of house of Hanover', 1.0)]\n",
            "object of t:\n",
            "[('Albert Augustus Charles', 1.0)]\n",
            "relation of t:\n",
            "[('husband', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "t = context.one('nuclear_family_g_trip_14295', 'nuclear_family_g_triple_t')\n",
        "print('triple t:')\n",
        "printd(t.eval())\n",
        "print('subject of t:')\n",
        "printd(t.nuclear_family_g_subj().eval())\n",
        "print('object of t:')\n",
        "printd(t.nuclear_family_g_obj().eval())\n",
        "print('relation of t:')\n",
        "printd(t.nuclear_family_g_rel().eval())\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "UpbGOHuDKJVF"
      },
      "source": [
        "This lets you follow relation sets using NQLs other primitives - for instance, to follow the *spouse* relation for Queen Victoria, we first find triples that have her as the subject, and something in the set *spouse* relations as the relation.  We can  then step from these triples (in this case, there's only) to the object."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "uL6Dk8xYKq2l",
        "outputId": "a89cced2-c7c2-4911-ee65-6c8cc97f93a0"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{'Albert Augustus Charles': 1.0}\n"
          ]
        }
      ],
      "source": [
        "queen_vics_spouse_triples = queen_vic.nuclear_family_g_subj(-1) \u0026 spouse.nuclear_family_g_rel(-1)\n",
        "queen_vics_spouse = queen_vics_spouse_triples.nuclear_family_g_obj()\n",
        "\n",
        "print(queen_vics_spouse.eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "oTlDWazNMCox"
      },
      "source": [
        "Similarly, for Henry VIII:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 105
        },
        "colab_type": "code",
        "id": "Hl99ruztMHVC",
        "outputId": "1f4562ca-5acf-4e1d-b987-9e00130a549e"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "triples:\n",
            "[('nuclear_family_g_trip_13470', 1.0), ('nuclear_family_g_trip_13472', 1.0), ('nuclear_family_g_trip_13473', 1.0), ('nuclear_family_g_trip_13474', 1.0), ('nuclear_family_g_trip_13476', 1.0), ('nuclear_family_g_trip_13478', 1.0)]\n",
            "wives:\n",
            "[('Anne of house of Boleyn', 1.0), ('Anne of_Cleves', 1.0), ('Catherine of house of Howard', 1.0), ('Catherine of house of Parr', 1.0), ('Catherine of_Aragon', 1.0), ('Jane of house of Seymour', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "henry8_spouse_triples = henry8.nuclear_family_g_subj(-1) \u0026 spouse.nuclear_family_g_rel(-1)\n",
        "print('triples:')\n",
        "printd(henry8_spouse_triples.eval())\n",
        "print('wives:')\n",
        "printd(henry8_spouse_triples.nuclear_family_g_obj().eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "21kaQ-TcT_LQ"
      },
      "source": [
        "# Subclassing NQLExpression"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "GSnj_BO5UEQN"
      },
      "source": [
        "Sometimes it's clearer to define methods that you can chain, rather than functions that you call.  For instance above we added \n",
        "\n",
        "```\n",
        "def child(x): return x.son() | x.daughter()\n",
        "def parent(x): return x.mother() | x.father()\n",
        "def grandfather(x): return parent(x).father()\n",
        "```\n",
        "allowing you to write\n",
        "```\n",
        "print child(queen_vic).eval()\n",
        "print grandfather(henry8).eval()\n",
        "```\n",
        "\n",
        "But it might be nicer to be able use the same syntax for both user-defined things like *child* and KG-defined things like *son*.  If course, we could easily define a functional version of the KG-based relations,  like so:\n",
        "\n",
        "\n",
        "\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "-dDRjaiXVDIR",
        "outputId": "5566a3cf-307c-42fd-9443-e873df902afe"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Alfred Ernest Albert', 1.0), ('Arthur William Patrick', 1.0), ('Edward_VII of house of Wettin', 1.0), ('Leopold George Duncan', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "def son_fun(x): return x.son()\n",
        "\n",
        "printd(son_fun(queen_vic).eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "adL1WeSYVkEp"
      },
      "source": [
        "Defining chaining-style operations is a little more complicated.  We need to subclass the NQLExpression class and add some new methods.  Then, because the *context* is a factory class, which generates expressions, we need to tell it to create instances of my subclass.  To avoid confustion I'll do this with a fresh context object."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "colab_type": "code",
        "id": "PufWHyjmWWn6",
        "outputId": "5eb24a3d-47af-40bf-e7bd-d4710b24f1cf"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "INFO:tensorflow:0 facts loaded\n",
            "[('Alfred Ernest Albert', 1.0), ('Alice Maud Mary', 1.0), ('Arthur William Patrick', 1.0), ('Beatrice Mary Victoria', 1.0), ('Edward_VII of house of Wettin', 1.0), ('Helena Augusta Victoria', 1.0), ('Leopold George Duncan', 1.0), ('Louise Caroline Alberta', 1.0), ('Victoria Adelaide Mary', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "class MyNQLExpression(nql.NeuralQueryExpression):\n",
        "  def child(self): return self.son() | self.daughter()\n",
        "\n",
        "#load a new copy of the context object\n",
        "context2 = nql.NeuralQueryContext()\n",
        "context2.expression_factory_class = MyNQLExpression\n",
        "relations = [\n",
        "  'aunt', 'brother', 'daughter', 'father', 'husband', 'mother', \n",
        "  'nephew', 'niece', 'sister', 'son', 'uncle', 'wife' \n",
        "]\n",
        "for r in relations:\n",
        "  context2.declare_relation(r, 'person_t', 'person_t')\n",
        "with open('/content/language_repo/language/nql/demos/data/royal92/royal_family.tsv') as f:\n",
        "  context2.load_kg(files=f)\n",
        "\n",
        "# now let's try using my new chaining-style method\n",
        "queen_vic2 = context2.one('Victoria of house of Hanover', 'person_t')\n",
        "printd(queen_vic2.child().eval())\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "K7DOLcCbfPWW"
      },
      "source": [
        "# Under the hood - NQL and TensorFlow"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "f8WxHlQNTPKG"
      },
      "source": [
        "## Weighted sets and counting paths"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "yA4IBQOyD8Kq"
      },
      "source": [
        "To understand the non-uniform weights that occur when we look for all the male or female people, we need to drill down into what's actually happening here as we evalulate code like. \n",
        "\n",
        "\n",
        "```\n",
        "x = context.all('person_t')\n",
        "x.mother().son() \u0026 x\n",
        "```\n",
        "\n",
        "\n",
        "Let's take it step by step.  The *all* sets are uniformly weighted..."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "rd93juUYEhfa",
        "outputId": "361ca956-66cc-4049-b201-6e3da5d2d7a9"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('(Daughter)', 1.0), ('(Frederick) Christian Charles', 1.0), ('(Sophia) Charlotte', 1.0), ('5sons_1dau', 1.0), ('Ada', 1.0), ('Adalberon of_Rheims', 1.0), ('Adalbert', 1.0), ('Adalbert #2', 1.0), ('Adam of_Rowallan of house of Mure', 1.0), ('Adela', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "e = context.all('person_t').eval()\n",
        "printd(e, 10)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "CjxDFqJgEsNv"
      },
      "source": [
        "...but when we take the next step, we end up with differing weights:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "mzEtjvDkEx5W",
        "outputId": "c6d7364b-fcd7-4c1f-d835-3f016001917f"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('(Sophia) Charlotte', 15.0), ('Ada', 3.0), ('Adela', 5.0), ('Adelaide Horatia Elizabeth of house of Seymour', 1.0), ('Adelaide Judith', 1.0), ('Adelaide Louisa Theresa', 4.0), ('Adelaide of_Savoy', 7.0), ('Adele of_Champagne', 1.0), ('Agatha #2', 3.0), ('Agnes', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "e = context.all('person_t').mother().eval()\n",
        "printd(e, 10)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "cedsl0tKE6Nz"
      },
      "source": [
        "The weights here correspond to the *number of children* each mother has, which we can check as follows (for a sample of the values)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 377
        },
        "colab_type": "code",
        "id": "XGa0UsPwFFDX",
        "outputId": "e7fac88d-0408-4f97-8373-4df945e4de9b"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "(Sophia) Charlotte has 15 kid(s):\n",
            "[('Adolphus of_Cambridge of house of Hanover', 1.0), ('Alfred of house of Hanover', 1.0), ('Amelia of house of Hanover', 1.0), ('Augusta Sophia of house of Hanover', 1.0), ('Augustus Frederick of house of Hanover', 1.0), ('Charlotte Augusta Matilda of house of Hanover', 1.0), ('Edward Augustus of house of Hanover', 1.0), ('Elizabeth of house of Hanover', 1.0), ('Ernest Augustus_I of house of Hanover', 1.0), ('Frederick of house of Hanover', 1.0), ('George_IV of house of Hanover', 1.0), ('Mary of house of Hanover', 1.0), ('Octavius of house of Hanover', 1.0), ('Sophia of house of Hanover', 1.0), ('William_IV Henry of house of Hanover', 1.0)]\n",
            "Ada has 3 kid(s):\n",
            "[('David of_Huntingdon', 1.0), ('Malcolm_IV the_Maiden', 1.0), ('Willaim_I the_Lion', 1.0)]\n",
            "Adela has 5 kid(s):\n",
            "[('Henry of_Winchester', 1.0), ('Matilda #5', 1.0), ('Stephen', 1.0), ('Theobald', 1.0), ('William #11', 1.0)]\n",
            "Adelaide Horatia Elizabeth of house of Seymour has 1 kid(s):\n",
            "[('Charles Robert of house of Spencer', 1.0)]\n",
            "Adelaide Judith has 1 kid(s):\n",
            "[('Charles_III the_Simple', 1.0)]\n",
            "Adelaide Louisa Theresa has 4 kid(s):\n",
            "[('Charlotte Augusta Louisa of house of Hanover', 1.0), ('Elizabeth Georgiana Adelaide of house of Hanover', 1.0), ('Twin-Boy_1', 1.0), ('Twin-Boy_2', 1.0)]\n",
            "Adelaide of_Savoy has 7 kid(s):\n",
            "[('Constance of_Toulouse #2', 1.0), ('Henry of_Beauvais', 1.0), ('Louis_VII the_Younger', 1.0), ('Philip #3', 1.0), ('Philip #4', 1.0), ('Pierre de_Courtenay', 1.0), ('Robert #4', 1.0)]\n",
            "Adele of_Champagne has 1 kid(s):\n",
            "[('Philip_II Augustus', 1.0)]\n",
            "Agatha #2 has 3 kid(s):\n",
            "[('Christina', 1.0), ('Edgar of house of Athling', 1.0), ('St_Margaret', 1.0)]\n",
            "Agnes has 1 kid(s):\n",
            "[('Agnes #2', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "mothers = context.all('person_t').mother().eval()\n",
        "for name,weight in sorted(mothers.items())[0:10]:\n",
        "    print('%s has %d kid(s):' % (name, weight))\n",
        "    printd(child(context.one(name,'person_t')).eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "qPBxtecVF7YD"
      },
      "source": [
        "What's happening is that while the set of keys is the set of all mothers, the weight for any person *x* indicate **the number of ways** that x can be \"demonstrated to be a mother\" --- i.e., when we started with the set *S* and followed the relation *mother*, the weights for *x* became the number of elements in *S* from which *x* could be reached via the *mother* relation.\n",
        "\n",
        "\n",
        "This behavior cascades - for example, if we evaluated *S.mother().mother()* the weight for each *x'* would be the number of people for whom *x'* is a maternal grandmother, and we will get more extreme weights."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 105
        },
        "colab_type": "code",
        "id": "1QR0YwYYHqoA",
        "outputId": "0d8ac8cc-1e5d-49fe-e391-8da86f97dfa5"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "moms:\n",
            "[('(Sophia) Charlotte', 15.0), ('Ada', 3.0), ('Adela', 5.0), ('Adelaide Horatia Elizabeth of house of Seymour', 1.0), ('Adelaide Judith', 1.0), ('Adelaide Louisa Theresa', 4.0), ('Adelaide of_Savoy', 7.0), ('Adele of_Champagne', 1.0), ('Agatha #2', 3.0), ('Agnes', 1.0)]\n",
            "grandmoms:\n",
            "[('Agatha #2', 5.0), ('Agnes', 1.0), ('Alexandra of house of Windsor', 1.0), ('Alexandra of_Denmark \"Alix\"', 3.0), ('Alexandra of_Greece', 1.0), ('Alice Maud Mary', 12.0), ('Alice de_Courtenay', 5.0), ('Alice of house of de_Toledo', 3.0), ('Alice of_Battenberg', 6.0), ('Amalie of_Wurttemberg', 4.0)]\n"
          ]
        }
      ],
      "source": [
        "mothers = context.all('person_t').mother()\n",
        "print('moms:')\n",
        "printd(mothers.eval(), 10)\n",
        "\n",
        "grandmothers = mothers.mother()\n",
        "print('grandmoms:')\n",
        "printd(grandmothers.eval(), 10)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "knei8u8a5b5O"
      },
      "source": [
        "In this colab, we implemented a special *printd* method to show the top weights in an evaluated NQL expression, so you would know exactly what was happening.  However, getting the top-weighted elements of an NQL entity set is common enough that you can do it with a special option to the *eval* method."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 71
        },
        "colab_type": "code",
        "id": "9wj8ZCcD5HkJ",
        "outputId": "eedeceff-9af5-4d6d-aabd-5e0880eabced"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "moms:\n",
            "[('(Sophia) Charlotte', 15.0), ('Anne of house of Stuart', 12.0), ('Eleanor of_Castile', 15.0), ('Elizabeth of house of Woodville', 12.0), ('Philippa of_Hainault', 12.0), ('Sophia Dorothea of house of Hanover', 13.0)]\n"
          ]
        }
      ],
      "source": [
        "m = mothers.eval(as_top=6)\n",
        "print('moms:')\n",
        "printd(m)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "YmBuJ8jSVRqe"
      },
      "source": [
        "While you can ignore the weights ,  the NQL implementors believes that this behavior is actually a feature and not a bug, as the weights often are useful information.  Here, for example, the weights reflect the number of descendants."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "QjiObZAHgeXg"
      },
      "source": [
        "If you want to get rid of the  non-uniform weights, one way would be to convert the weighted NQL expression to Tensorflow - which can be done with the *.tf* property -- clip the weights using the appropriate Tensorflow op, and then convert back to NQL,  as follows."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "3sXrfNQUg4IN",
        "outputId": "f0d8b5ef-e067-4b6d-8f24-d102caeb2ac3"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Adalbert', 1.0), ('Adalbert #2', 1.0), ('Adolphe of_Luxembourg', 1.0), ('Adolphus', 1.0), ('Adolphus 2nd', 1.0), ('Adolphus Frederick_V', 1.0), ('Adolphus of_Cambridge of house of Hanover', 1.0), ('Agustus', 1.0), ('Albert', 1.0), ('Albert Augustus Charles', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "weighted_men = context.all('person_t').mother().son()\n",
        "weighted_men_tensor = weighted_men.tf\n",
        "men_tensor = tf.clip_by_value(weighted_men_tensor, 0.0, 1.0)\n",
        "men = context.as_nql(men_tensor,'person_t')\n",
        "\n",
        "printd(men.eval(), 10)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "5VKYxY5d6M_w"
      },
      "source": [
        "If you're just jumping out to Tensorflow for a short time, it's sometimes more concise to use the *tf_op* method, which lets you use any Python function that transforms a tensor to one with the same shape.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "JEIA1upn6VR9",
        "outputId": "3e2f1713-1b68-4d1f-c4c9-d44bb145fc35"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Adalbert', 1.0), ('Adalbert #2', 1.0), ('Adolphe of_Luxembourg', 1.0), ('Adolphus', 1.0), ('Adolphus 2nd', 1.0), ('Adolphus Frederick_V', 1.0), ('Adolphus of_Cambridge of house of Hanover', 1.0), ('Agustus', 1.0), ('Albert', 1.0), ('Albert Augustus Charles', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "men2 = weighted_men.tf_op(lambda t:tf.clip_by_value(t, 0.0, 1.0))\n",
        "printd(men2.eval(), 10)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "7m5ae76ureUy"
      },
      "source": [
        "We discuss the possible Tensorflow - NQL conversion processes below."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "3m10zmsqY_37"
      },
      "source": [
        "## Following relations via sparse matrix multiplication"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "YqPVQiEZ1ets"
      },
      "source": [
        "Under the hood, what's going on here is that each weighted set is represented internally as a vector - generally a sparse vector, with one component for each entity.   To make it a little more complicated, NQL is, like most neural systems, set up to operate on minibatches, so the internal representation of a single set is a *matrix*.  For these queries, the matrix has one row, and *n* columns, where *n* is the number of *person_t* entiities.  A relation is represented as a matrix, and following a relation is implemented as a vector-matrix multiplication.\n",
        "\n",
        "Let's drill down into this a little more.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "gfw9I3tgUVDN"
      },
      "source": [
        "There are two equivalent ways to get at this 'raw' representation.  One is to pass the *as_dicts=False* option into the *eval* method for an NQL expression."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 68
        },
        "colab_type": "code",
        "id": "YcjlEgQ21uUO",
        "outputId": "5110e226-2184-4de9-86bc-a4f277a04f12"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "m [[0. 0. 0. ... 0. 0. 0.]]\n",
            "m.shape (1, 3007)\n",
            "nonzeros in m (array([0, 0, 0, 0, 0, 0]), array([1039, 1064, 1069, 1071, 1074, 1079]))\n"
          ]
        }
      ],
      "source": [
        "m = henry8.wife().eval(as_dicts=False)\n",
        "print('m', m)\n",
        "print('m.shape', m.shape)\n",
        "print('nonzeros in m', np.nonzero(m))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "w69We_fuUaws"
      },
      "source": [
        "Alternatively we can get at the underlying tensorflow expression, and evaluate it:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 68
        },
        "colab_type": "code",
        "id": "AELvbs6oUnan",
        "outputId": "7dd9add9-0d74-4008-ed20-db0ba506e028"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "m [[0. 0. 0. ... 0. 0. 0.]]\n",
            "m.shape (1, 3007)\n",
            "nonzeros in m (array([0, 0, 0, 0, 0, 0]), array([1039, 1064, 1069, 1071, 1074, 1079]))\n"
          ]
        }
      ],
      "source": [
        "m = henry8.wife().tf.numpy()\n",
        "print('m', m)\n",
        "print('m.shape', m.shape)\n",
        "print('nonzeros in m', np.nonzero(m))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "7ELWzJv-V0Xv"
      },
      "source": [
        "In this representation, the nonzero column indices are ids for the wives of Henry VIII.  We can decode these ids using the *context* object.  (The row indices are all zero, since the matrix has only one row.)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 173
        },
        "colab_type": "code",
        "id": "IYQ0_SnPWMOG",
        "outputId": "fec0676c-bb22-4a01-9475-5a3042e6efd0"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "person with id 1039 is Catherine of_Aragon\n",
            "person with id 1064 is Anne of house of Boleyn\n",
            "person with id 1069 is Jane of house of Seymour\n",
            "person with id 1071 is Anne of_Cleves\n",
            "person with id 1074 is Catherine of house of Howard\n",
            "person with id 1079 is Catherine of house of Parr\n",
            "target set is:\n",
            "dict_keys(['Catherine of_Aragon', 'Anne of house of Boleyn', 'Jane of house of Seymour', 'Anne of_Cleves', 'Catherine of house of Howard', 'Catherine of house of Parr'])\n"
          ]
        }
      ],
      "source": [
        "row_indices, col_indices = np.nonzero(henry8.wife().tf.numpy())\n",
        "for person_id in col_indices.tolist():\n",
        "  print('person with id %d is %s' % (person_id, context.get_entity_name(person_id, 'person_t')))\n",
        "# double check these values are correct\n",
        "print('target set is:')\n",
        "printd(henry8.wife().eval().keys())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "onES62kCrrCm"
      },
      "source": [
        "As an alternative, we could convert m back to an NQL expression to examine it."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 54
        },
        "colab_type": "code",
        "id": "0i5WDMExryWy",
        "outputId": "c99b043e-b13b-4013-8793-148ea9ca8319"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[('Anne of house of Boleyn', 1.0), ('Anne of_Cleves', 1.0), ('Catherine of house of Howard', 1.0), ('Catherine of house of Parr', 1.0), ('Catherine of_Aragon', 1.0), ('Jane of house of Seymour', 1.0)]\n"
          ]
        }
      ],
      "source": [
        "printd(context.as_nql(m, 'person_t').eval())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ZA-WXXRzXRCB"
      },
      "source": [
        "Every relation is represented as a sparse matrix.  Below we'll look at the sparse matrix for the *wife* relation, and decode a few indices as a check."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 173
        },
        "colab_type": "code",
        "id": "j2KAeX_UX8f6",
        "outputId": "89727325-fd1a-4661-80ad-7b8de78dc0a5"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "SparseTensor(indices=tf.Tensor(\n",
            "[[   0    1]\n",
            " [  12    4]\n",
            " [   2   24]\n",
            " ...\n",
            " [2995 2996]\n",
            " [3001 3002]\n",
            " [3005 3006]], shape=(1138, 2), dtype=int64), values=tf.Tensor([1. 1. 1. ... 1. 1. 1.], shape=(1138,), dtype=float32), dense_shape=tf.Tensor([3007 3007], shape=(2,), dtype=int64))\n"
          ]
        }
      ],
      "source": [
        "wife_tensor = context.get_tf_tensor('wife')\n",
        "print(wife_tensor)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 119
        },
        "colab_type": "code",
        "id": "BCclHuBYZwMF",
        "outputId": "b8c4d307-b4ca-40cf-ab50-53fbe7cbb6cf"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "id: 1039 name: Catherine of_Aragon\n",
            "id: 1064 name: Anne of house of Boleyn\n",
            "id: 1069 name: Jane of house of Seymour\n",
            "id: 1071 name: Anne of_Cleves\n",
            "id: 1074 name: Catherine of house of Howard\n",
            "id: 1079 name: Catherine of house of Parr\n"
          ]
        }
      ],
      "source": [
        "henry8_id = context.get_id('Henry_VIII of house of Tudor', 'person_t')\n",
        "for row_index,col_index in wife_tensor.indices.numpy().tolist():\n",
        "  if col_index==henry8_id:\n",
        "      print('id: %d name: %s' % (row_index, context.get_entity_name(row_index, 'person_t')))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "zBh5C4ABbLIi"
      },
      "source": [
        "*Aside:* The storage is a little counterintuitive: instead of *wife_matrix* mapping from Henry to Ann Bolyn, for instance, by making the husbands the row indices and the wives the column indices, NQL stores indices in the opposite order.  This is because Tensorflow doesn't support multiplying a dense vector **v**  by a sparse matrix **S**, only multiplication of **S** by **v**.  So instead of computing **v * S**, we must compute something more like\n",
        "\n",
        "```\n",
        "transpose( transpose(S) * transpose(v) )\n",
        "```\n",
        "Hence storing *transpose(S)* directly is somewhat more efficient.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "iRTPHBu4g4RS"
      },
      "source": [
        "So finally, if we want to simulate *henry8.wife()*, we can do it as follows."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "_fy98Z3ChG_o",
        "outputId": "bc192f12-6fb0-4556-be5a-a74dacfa1c95"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "(array([0, 0, 0, 0, 0, 0]), array([1039, 1064, 1069, 1071, 1074, 1079]))\n"
          ]
        }
      ],
      "source": [
        "v = henry8.eval(as_dicts=False)\n",
        "S = wife_tensor\n",
        "henrys_wives = tf.transpose(tf.sparse.sparse_dense_matmul(S, tf.transpose(v)))\n",
        "print(np.nonzero(henrys_wives.numpy()))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "WOPFp5i9-WoY"
      },
      "source": [
        "If you look above, you can see that this is indeed the correct result."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "uhyVw_9rizNF"
      },
      "source": [
        "Finally,  set union **v | w** is implemented as vector sum, set intersection **v \u0026 w** is component-wise vector multiplication, and \n",
        "the **v**.*if_any*(**w**) construct is implemented by multiplying the vector **v** by the sum of the value of the components of **w**.   The last one is worth a little discussion, so let's break down the example  code above\n",
        "\n",
        "\n",
        "```\n",
        "def opposite_sex_parent(x):\n",
        "  return x.father().if_any(female(x)) | x.mother().if_any(male(x))\n",
        "```\n",
        "\n",
        "which if we expand out the definitions of *femaile* and *male* is actually\n",
        "\n",
        "\n",
        "```\n",
        "def opposite_sex_parent(x):\n",
        "  return x.father().if_any(x.mother().daughter() \u0026 x) \n",
        "       | x.mother().if_any(x.father().son() \u0026 x)\n",
        "```\n",
        "\n",
        "The steps in the computation given **v** might be\n",
        "\n",
        "1.   Compute the set of all female children **sisters** of the mother of **v** with two matrix multiplications:  **sisters =v M D**, where **M** is the motherhood matrix and **D** is the daughter matrix.\n",
        "2.   Compute the componentwise product **sisters * v**.  If **v** is male, this is the all-zeros vector, so *a = sum*(**sisters  * v**) = 0.  Otherwise the product will be equivalent to **v** so *a=1*.\n",
        "3. Likewise compute ** brothers = v F S ** and *b = sum*(**brothers * v**)\n",
        "4. Finally compute (*a* ** x F **) + (*b* **x M**)\n",
        "\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "2ZCCAlTSw-qK"
      },
      "source": [
        "Below is a summary of the implementations of all the NQL operators.  The only things not discussed above are \n",
        "\n",
        "1. The *none* construct, which returns an empty set;\n",
        "2. The *x.jump_to_xxx* constructs, which let you construct singleton sets, empty sets, and universal sets from an expression, rather than using a context object.\n",
        "3. The operation *x.filtered_by(relation, entity_name)* which is shorthand for\n",
        "\n",
        "\n",
        "```\n",
        "   x \u0026 x.jump_to_one(entity_name, type_name).relation(-1)\n",
        "```\n",
        "where *type_name* is the type declared for the second arguments of *relation*.\n",
        "\n",
        "4. The alternative operators for union and intersection, and the alternative names for *filtered_by* and *if_any*, which are sometimes useful when you want to emphasize the behavior on weighted sets.\n",
        "\n",
        "\u003e NQL                              | Matrix operations\n",
        "\u003e ---------------------------------|------------------\n",
        "\u003e `context.one('bob', 'person_t')` \u003cbr/\u003e `x.jump_to('bob', 'person_t')` | $v_\\text{bob}$, one hot vector for entity  'bob'\n",
        "\u003e `context.all('person_t')` \u003cbr/\u003e `x.jump_to_all('person_t')`| k-hot vector for set of all element of type 'person' i.e. a ones vector\n",
        "\u003e `context.none('person_t')` \u003cbr/\u003e `x.jump_to_none('person_t')` | k-hot vector for empty set of element of type 'person' i.e. a zeros vector\n",
        "\u003e `x.r()` \u003cbr/\u003e `x.follow('r')` | $x \\cdot M_r$\n",
        "\u003e `x \\| y` \u003cbr/\u003e `x + y` | $x + y$\n",
        "\u003e `x \u0026 y` \u003cbr/\u003e `x * y` | $x * y$ \u003cbr/\u003e Hadamard a.k.a. component-wise product\n",
        "\u003e `x.filtered_by('r', 'bob')` \u003cbr/\u003e `x.weighted_by('r', 'bob')` | $x * \\left( v_\\text{bob} \\cdot M_r^T \\right)$\n",
        "\u003e `x.if_any(y)` \u003cbr/\u003e `x.weighted_by_sum(y)` | $x * \\sum y$"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "Introduction to NQL",
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
